package net.crazyadam.audio;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Copyright by <a href="http://crazyadam.net"><em><i>Joseph J.C. Tang</i></em></a> <br/>
 * Email: <a href="mailto:jinntrance@gmail.com">jinntrance@gmail.com</a>
 *
 * @author joseph
 * @version 1.0
 * @since 9/19/12 4:53 PM
 */
public class SpectrumWindow implements Serializable {
    /**
     * 音频采样率
     */
    private int sampleRate;
    /**
     * 频域范围内的精度，越大精度越大。
     */
    private int blockSize;
    /**
     * 收集到的所有数据。
     */
    private List<float[]> spectrumDatas;
    /**
     * 自带音频频率
     */
    private static final float crestFrequency =Constant.FREQUENCY;
    private int startIndex(){
      return indexOf(crestFrequency); //截取将要使用的频段数组  
    }
    private int endIndex() {
        return indexOf(crestFrequency + 1000);
    } 

    public SpectrumWindow(int size,int rate) {
        sampleRate= rate;
        blockSize = size;
        spectrumDatas = new ArrayList<float[]>();
    }

    public void setSampleRate(int sampleRate) {
        this.sampleRate = sampleRate;
    }

    public void record(float[] spectrumData) {
        spectrumDatas.add(Arrays.copyOf(spectrumData, spectrumData.length)); //must make new copy,or
    }

    /**
     *  处理并找到最大频率偏倚。
     * @return 0 如果沒有高頻，則返回0
     */
    public float processData() {
        if(!ready())
            return 0f;
        float[][] allDatas = getDataForEachFrequenct();
        int largestIndex =findLargestIndex(allDatas);
        float frequency = indexToFrequency(largestIndex);
        System.out.println(frequency + "Hz largestIndex");
        return frequency;
    }

    /**
     *
     * @return 每行存一個固定頻率隨着时间变化的值
     */
    private float[][] getDataForEachFrequenct() {
        int startIndex=startIndex();
        int endIndex=endIndex();
        int freSize = endIndex - startIndex;
        int sampleSize=spectrumDatas.size();
        float[][] allDatas;
        synchronized (this){ //同步，防止声音接收的Thread写入引发的问题。
        allDatas = new float[freSize][sampleSize];//每行存一個固定頻率
        for (int k = 0; k < sampleSize; k++) {
            for (int j = 0; j < freSize; j++) {
                allDatas[j][k] = spectrumDatas.get(k)[j+startIndex];
            }
        }
        }
        return allDatas;
    }

    /**
     *
     * @param allDatas 单行为某一频率在所有时间下的记录。
     * @return 找到甩动引起的最大频偏
     */
    private int findLargestIndex(float[][] allDatas) {
        int largestIndex = 0;
        float[] deltas = new float[allDatas.length];
        int sigh = 0; //波峯连续下降移動最右位置标识位

        float threshold = 0;
        if (allDatas.length > 0)
            threshold = MathFunction.max(allDatas[0]) / 10;//取波峰振幅1/10作为阀值，截取接近波峰的值即可。
        for (int m = 0; m < allDatas.length; m++) {
            float[] d = allDatas[m];
            deltas[m] = MathFunction.max(d) - threshold;//某一个频率波峯最大值，即为晃动后频偏引起。
            if (deltas[m] > 0 ) { //选取值时，第一种情况18kHz波峰连续的波形降到阀值以下后就不再取值。sigh==1就超出范围。。
                if(0==sigh || deltas[m]>2*threshold) //Dopper文章中第二種情況，振幅大於峯值的3/10.也算。
                largestIndex = m;
            }
            else{
                sigh = -1;
            }
        }
        return largestIndex;
    }

    /**
     *
     * @param largestIndex index
     * @return 把对应index数据转换为指定频率。
     */
    private float indexToFrequency(int largestIndex) {
        return (largestIndex + startIndex()) * Constant.SAMPLE_RATE*1.0f / blockSize;
    }

    /**
     *
     * @return 看是否已经有高频声音发出了。
     */
    public boolean ready(){
        if(0==spectrumDatas.size())
            return false;
        int last = spectrumDatas.size() - 1;
        return  (spectrumDatas.get(last)[startIndex()]/10)>=spectrumDatas.get(last)[endIndex()];
    }

    public void clear() {
        spectrumDatas.clear();
    }

    public int indexOf(float frequency) {
        if (sampleRate > frequency) {
            return Math.round(frequency * blockSize / sampleRate);
        } else return 0;
    }

    public void setBlockSize(int blockSize) {
        this.blockSize = blockSize;
    }
}
